#ifndef SIMPLE_SUPPORT_ITERATOR_OUT_ACCUMULATE_HPP
#define SIMPLE_SUPPORT_ITERATOR_OUT_ACCUMULATE_HPP

#include <type_traits> // std::remove_reference_t std::enable_if_t std::is_same_v
#include <iterator> // std::iterator_trait std::output_iterator_tag
#include <functional> // std::plus
#include <utility> // std::forward

#include "../function_utils.hpp" // support::invoke

namespace simple::support
{

	template <typename Accumulator, typename Op>
	struct out_accumulate
	{
		using value_type = Accumulator;
		using difference_type = void;
		using pointer = std::remove_reference_t<Accumulator>*;
		using reference = Accumulator&;
		using iterator_category = std::output_iterator_tag; //NOTE: except can read the accumulated value

		Accumulator accumulator;
		Op operation;

		template <typename A, typename O = std::plus<>,
			std::enable_if_t<std::is_same_v<A,Accumulator>>* = nullptr>
		explicit out_accumulate(A&& accumulator, O&& operation = std::plus<>{}) :
			accumulator(std::forward<A>(accumulator)),
			operation(std::forward<O>(operation))
		{}

		out_accumulate& operator++() { return *this; }
		out_accumulate operator++(int) { out_accumulate prev = *this; ++(*this); return prev; }
		out_accumulate& operator--() { return *this; }
		out_accumulate operator--(int) { out_accumulate prev = *this; --(*this); return prev; }

		class proxy
		{
			Accumulator& accumulator;
			Op& operation;

			proxy(Accumulator& accumulator, Op& operation)
				: accumulator(accumulator), operation(operation) {}
			friend struct out_accumulate;
			public:
			proxy() = delete;
			proxy(const proxy&) = delete;
			proxy(const proxy&&) = delete;
			proxy& operator=(const proxy&) = delete;
			proxy& operator=(const proxy&&) = delete;

			template <typename T>
			const proxy& operator=(T&& x) const
			{
				accumulator = support::invoke(operation, accumulator, std::forward<T>(x));
				return *this;
			}

			operator reference() const
			{
				return accumulator;
			}

		};

		const proxy operator*()
		{
			return {accumulator, operation};
		}
	};
	template <typename A, typename Op> out_accumulate(A&&, Op&&)
		-> out_accumulate<A, Op>;
	template <typename A> out_accumulate(A&&)
		-> out_accumulate<A, std::plus<>>;

} // namespace simple::support

#endif /* end of include guard */
